#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
First trying to build BlogoSerV
"""

#import re
TYP_NIL      = 0
TYP_STRUCT   = 1
TYP_STRING   = 2
TYP_INTEGER  = 3
TYP_DOUBLE   = 4
TYP_DATETIME = 5
TYP_BOOLEAN  = 6
TYP_BASE64   = 7
TYP_ARRAY    = 8

import xml.dom.minidom
import datetime
import re

class XML_RPC( ):
  """
# import && init class XML_RPC
>>> import libxmlrpc
>>> cc = libxmlrpc.XML_RPC( )

# example list of args /python's types/
>>> args = [ 'test', 25, {'bool':False, 'have':['ee',48]} ]

# get XML-RPC argument list
>>> xargs = [ cc.convPy2XML( arg ) for arg in args ]
>>> xargs
[(2, 'test'), (3, 25), (1, [(6, 'bool', 0), (8, 'have', [(2, 'ee'), (3, 48)])])]

# forming methodCall XML doc
>>> res = cc.methodCall( 'test', xargs )
>>> print res
<?xml version="1.0"? encoding="utf-8">
<methodCall>
  <methodName>test</methodName>
  <params>
    <param>
      <value><string>test</string></value>
    </param>
    <param>
      <value><i4>25</i4></value>
    </param>
    <param>
      <value><struct>
        <member>
          <name>bool</name>
          <value><boolean>0</boolean></value>
        </member>
        <member>
          <name>have</name>
          <value><array>
        <data>
          <value><string>ee</string></value>
          <value><i4>48</i4></value>
        </data>
      </array></value>
        </member>
      </struct></value>
    </param>
  </params>
</methodCall>

# see result after parsing doc
>>> xres = cc.parseCall( res )
>>> print xres
(u'test', [(2, u'test'), (3, 25), (1, [(6, u'bool', False), (8, u'have', [(2, u'ee'), (3, 48)])])])

# convert back to python's types
>>> argsStrikeBack = [ cc.convXML2Py( arg ) for arg in xres[1] ]
>>> print argsStrikeBack
[u'test', 25, {u'bool': False, u'have': [u'ee', 48]}]

  """

  def __init__( self ):
    self.SPACER       = '  '
    self.reDateTime = re.compile( '([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})' )
    self.reBase64   = re.compile( '[A-Za-z0-9+/=]\+' )
  
  
  # reDateTime = re.compile( '([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})' );
  # res = reDateTime.search( '20100526T15:17:35' )
  # >>> res.groups()
  # ('2010', '05', '26', '15', '17', '35')

  # reBase64   = re.compile( '[A-Za-z0-9+/=]\+' )
  
  def convPy2XML( self, param ): # param
  
    #if param is None: # nil
    if param is None: # nil
      return ( TYP_NIL, '' )
  
    #elif param.__class__() == {}: # struct
    elif type( param ) is dict: # struct
      res = ( TYP_STRUCT, [] )
      for item in param.keys():
        typ = self.convPy2XML( param[item] )
        res[1].append( ( typ[0], item, typ[1] ) )
  
      return res
  
    #elif param.__class__() == '': # string
    elif type( param ) is str or type( param ) is unicode: # string
      return ( TYP_STRING, param )
  
    #elif param.__class__() == []: # array
    elif type( param ) is list: # array
      res = ( TYP_ARRAY, [] )
      for item in param:
        res[1].append( self.convPy2XML( item ) )
  
      return res
  
    #elif param.__class__() == False and ( param is True or param is False ): # boolean
    elif type( param ) is bool: # boolean
      if param: # true
        return ( TYP_BOOLEAN, 1 )
      else:
        return ( TYP_BOOLEAN, 0 )
  
    #elif param.__class__() == 0: # int
    elif type( param ) is int: # i4
      return ( TYP_INTEGER, param )
  
    #elif param.__class__() == 0.0: # double
    elif type( param ) is float: # double
      return ( TYP_DOUBLE, param )

    elif type( param ) is datetime.datetime: # datetime
      return ( TYP_DATETIME, format( param, '%Y%m%dT%H:%M:%S' ) )
  
    #elif type( param ) is datetime.date: # date as datetime
    #  return ( TYP_DATETIME, format( param, '%Y%m%dT%H:%M:%S' ) )

    elif type( param ) is file: # file aka BASE64
      import base64
      return ( TYP_BASE64, base64.encodestring( param.read() ) )

    else:
      return param
  
  def convXML2Py( self, param ): # param
 
    # here could be error while parsing values
 
    if param[0] == TYP_NIL:      # None
      return None
  
    elif param[0] == TYP_STRUCT:   # dict
      res = {}
      for item in param[1]:
        res[item[1]] = self.convXML2Py( ( item[0], item[2] ) )
      return res
        
  
    elif param[0] == TYP_STRING:   # string
      return param[1]
  
    elif param[0] == TYP_INTEGER:  # int
      return int( param[1] )
  
    elif param[0] == TYP_DOUBLE:   # float
      return float( param[1] )
  
    elif param[0] == TYP_DATETIME: # datetime
      res = reDateTime.search( param[1] )
      return datetime.datetime( res[0], res[1], res[2], res[3], res[4], res[5] )
  
    elif param[0] == TYP_BOOLEAN:  # bool
      if param[1]:
        return True
      else:
        return False
  
    elif param[0] == TYP_BASE64:   # string
      import base64
      try:
        return base64.decodestring( param[1] )
      except:
        return param[1]
  
    elif param[0] == TYP_ARRAY:    # list
      res = []
      for item in param[1]:
        res.append( self.convXML2Py( item ) )
      return res
 

 
  def extractParam( self, subxmlDom ):

    try:
      value = [ node for node in subxmlDom.childNodes if node.nodeType == node.ELEMENT_NODE ][0]
    except: # malformed XML-RPC
      return None

    if value.tagName == 'nil':
      return ( TYP_NIL, '' )

    elif value.tagName == 'struct':
      # init res list var
      res = []

      # collect members from struct
      members = [ node for node in value.childNodes if node.nodeType == node.ELEMENT_NODE and  node.tagName == 'member' ]

      # gather names && values ( recursively, certainly )
      for member in members:
        try:
          name = [ node.childNodes[0].data for node in member.childNodes if node.nodeType == node.ELEMENT_NODE and node.tagName == 'name' ][0]
        except: # malformed XML-RPC
          return None

        valueNode = [ node for node in member.childNodes if node.nodeType == node.ELEMENT_NODE and node.tagName == 'value' ][0]
        value = self.extractParam( valueNode )

        if value is None: # malformed XML-RPC
          return None

        res.append( ( value[0], name, value[1] ) )

      #res.append(  )
      return ( TYP_STRUCT, res )

    elif value.tagName == 'string': # correction of <string></string>
      if len( value.childNodes ) == 0:
        return ( TYP_STRING, '' )
      else:
        return ( TYP_STRING, value.childNodes[0].data )

    elif value.tagName == 'i4':
      return ( TYP_INTEGER, int( value.childNodes[0].data ) )

    elif value.tagName == 'double':
      return ( TYP_DOUBLE, float( value.childNodes[0].data ) )

    elif value.tagName == 'dateTime.iso8601':
      return ( TYP_DATETIME, value.childNodes[0].data ) # it simple string, yet

    elif value.tagName == 'boolean':
      return ( TYP_BOOLEAN, bool( int( value.childNodes[0].data ) ) )

    elif value.tagName == 'base64':
      return ( TYP_BASE64, value.childNodes[0].data )

    elif value.tagName == 'array':
      res = []
      try:  # malformed XML-RPC call
        dataNode = [node for node in value.childNodes if node.nodeType == node.ELEMENT_NODE and node.tagName == 'data'][0]
      except:
        return None

      # dataNode == data element
      values = [ node for node in dataNode.childNodes if node.nodeType == node.ELEMENT_NODE and node.tagName == 'value' ]
      for node in values:
        res.append( self.extractParam( node ) )

      return ( TYP_ARRAY, res )

    else: # malformed XML-RPC
      print 'malformed XML-RPC: else branch elected'
      return None


  def prepareParam( self, arg ):
    res = ''

    if   arg[0] == TYP_NIL: # ( TYP_NIL, '' )
      res += '<nil/>' # % ( 3*self.SPACER )
  
    elif arg[0] == TYP_STRUCT: # ( TYP_STRUCT, [(TYP_STRING, name, value), (TYP_STRING, name, value)] )
      res += '<struct>\n'
  
      for item in arg[1]:
        res += '%s<member>\n%s<name>%s</name>\n%s<value>%s</value>\n%s</member>\n' % ( 4*self.SPACER, 5*self.SPACER, item[1], 5*self.SPACER,\
            self.prepareParam( ( item[0], item[2] ) ), 4*self.SPACER )
  
      res += '%s</struct>' % ( 3*self.SPACER )
  
    elif arg[0] == TYP_STRING:
      res += '<string>%s</string>' % ( arg[1] )
  
    elif arg[0] == TYP_INTEGER:
      res += '<i4>%s</i4>' % ( arg[1] )
  
    elif arg[0] == TYP_DOUBLE:
      res += '<double>%s</double>' % ( arg[1] )
  
    elif arg[0] == TYP_DATETIME:
      res += '<dateTime.iso8601>%s</dateTime.iso8601>' % ( arg[1] )
  
    elif arg[0] == TYP_BOOLEAN:
      res += '<boolean>%s</boolean>' % ( arg[1] )
  
    elif arg[0] == TYP_BASE64:
      res += '<base64>%s</base64>' % ( arg[1] )
  
    elif arg[0] == TYP_ARRAY:
      res += '<array>\n%s<data>\n' % ( 4*self.SPACER )
  
      for item in arg[1]:
        res += '%s<value>%s</value>\n' % ( 5*self.SPACER, self.prepareParam( item ) )
  
      res += '%s</data>\n%s</array>' % ( 4*self.SPACER, 3*self.SPACER )
  
    return res
  
  def parseCall( self, strxml ):
    # init res list var
    res = []
    # parse XML doc
    xmlDom = xml.dom.minidom.parseString( strxml )
    del strxml

    # get methodName
    methodName = xmlDom.getElementsByTagName( 'methodName' )[0].childNodes[0].data
    # pickup param
    params = xmlDom.getElementsByTagName( 'param' )

    # get sub-param element named value
    #values = [ param.childNodes for param in params if param. ]

    # collect all value's of param parent element node
    for param in params:
      # iterate all value from current param
      for value in param.childNodes:
        # pick up item if nodeType is element
        if value.nodeType == value.ELEMENT_NODE and value.tagName == 'value':
          # read tagName && extract values
          res.append( self.extractParam( value ) )
          # break cycle on success. It must be at least one and no more! or XML-RPC malformed error should be raised
          break

    return ( ( xmlDom.version, xmlDom.encoding ), methodName, res )

  def parseResponce( self, strxml ):
    # init res list var
    errcode = 0
    res = []
    # parse XML doc
    xmlDom = xml.dom.minidom.parseString( strxml )
    del strxml

    resArgs = [node for node in xmlDom.firstChild.childNodes if node.nodeType == node.ELEMENT_NODE][0]

    if resArgs.tagName == 'params': # positive
      errcode = 0
      # pickup param
      params = [ param for param in resArgs.getElementsByTagName( 'param' ) if param.nodeType == xmlDom.ELEMENT_NODE ]
      for param in params:
        for value in param.childNodes:
          if value.nodeType == xmlDom.ELEMENT_NODE and value.tagName == 'value':
            res.append( self.extractParam( value ) )
      
#      #res = [ value for value in [ child for child in [param for param in params] ] if value.nodeType == node.ELEMENT_NODE and value.tagName == 'value' ]
#      print '[0]', params
#      # collect all value's of param parent element node
#      for param in params:
#        print '[-1]', param
#        # iterate all value from current param
#        for value in param.childNodes:
#          print '[-2]', value
#          # pick up item if nodeType is element
#          res = [ self.extractParam( resvalue ) for resvalue in value.childNodes if resvalue.nodeType == node.ELEMENT_NODE and resvalue.tagName == 'value' ]
#        #  if value.nodeType == value.ELEMENT_NODE and value.tagName == 'value':
#        #    # read tagName && extract values
#        #    res.append( self.extractParam( value ) )
#        #    # break cycle on success. It must be at least one and no more! or XML-RPC malformed error should be raised
#        #    break

    elif resArgs.tagName == 'fault': # negative
      errcode = 1
      res = [ self.extractParam( value ) for value in resArgs.childNodes if value.nodeType == node.ELEMENT_NODE and value.tagName == 'value' ]
        

    else: # error malformed XML-RPC responce
      pass

    return ( ( xmlDom.version, xmlDom.encoding ), errcode, res )
 
  def methodCall( self, name, args=[] ):
    # name = string
    # args = [ ( TYP_NIL, '' ),
    # ( TYP_STRUCT, [ ( TYP_NIL, 'test', 'value' ),
    # ( TYP_STRING, 'testing', 'test' ) ] ),
    # ( TYP_ARRAY, [ ( TYP_NIL, 0 ), ( TYP_STRING, 'work' ), ( TYP_DOUBLE, 0 ) ] ) ]
    res = '<?xml version="1.0" encoding="utf-8"?>\n<methodCall>\n%s<methodName>%s</methodName>\n%s<params>\n' % ( 1*self.SPACER, name, 1*self.SPACER )
  
    for arg in args:
      #arg = convertTypes( arg )
      res += '%s<param>\n%s<value>%s</value>\n%s</param>\n' % ( 2*self.SPACER, 3*self.SPACER, self.prepareParam( arg ), 2*self.SPACER )
  
    res += '%s</params>\n</methodCall>' % ( 1*self.SPACER )
    return res
  
  def methodResponse_ok( self, args ):
    if not type(args) is list:
      return None

    res = '<?xml version="1.0" encoding="utf-8"?>\n<methodResponse>\n%s<params>\n' % ( 1*self.SPACER )
  
    for arg in args:
      res += '%s<param>\n%s<value>%s</value>\n%s</param>\n' % ( 2*self.SPACER, 3*self.SPACER, self.prepareParam( arg ), 2*self.SPACER )
  
    res += '%s</params>\n</methodResponse>' % ( 1*self.SPACER )
    return res
  
  def methodResponse_fault( self, args ):
    if args.__class__() != []:
      return None

    res = '<?xml version="1.0" encoding="utf-8"?>\n<methodResponse>\n%s<fault>\n' % ( 1*self.SPACER );

    for arg in args:
      res += '%s<value>%s</value>\n' % ( 2*self.SPACER, self.prepareParam( arg ) )

    res += '%s</fault>\n</methodResponse>' % ( 1*self.SPACER )
    return res
 

"""
>>> args = ['string', 25, False, {'bool':True, 'list':['test', None]}]
>>> xargs = [ cc.convPy2XML( arg ) for arg in args ]
>>> print xargs
[(2, 'string'), (3, 25), (6, 0), (1, [(8, 'list', [(2, 'test'), (0, '')]), (6, 'bool', 1)])]
>>> res = cc.methodCall( 'test', xargs )
>>> print res
<?xml version="1.0"? encoding="utf-8">
<methodCall>
  <methodName>test</methodName>
  <params>
    <param>
      <value><string>string</string></value>
    </param>
    <param>
      <value><i4>25</i4></value>
    </param>
    <param>
      <value><boolean>0</boolean></value>
    </param>
    <param>
      <value><struct>
        <member>
          <name>list</name>
          <value><array>
        <data>
          <value><string>test</string></value>
          <value><nil/></value>
        </data>
      </array></value>
        </member>
        <member>
          <name>bool</name>
          <value><boolean>1</boolean></value>
        </member>
      </struct></value>
    </param>
  </params>
</methodCall>
>>> xres = cc.parseCall( res )
>>> print xres
(u'test', [(2, u'string'), (3, 25), (6, False), (1, [(8, u'list', [(2, u'test'), (0, '', '')]), (6, u'bool', True)])])
>>> argsback = [ cc.convXML2Py( arg ) for arg in xres[1] ]
>>> print argsback
[u'string', 25, False, {u'bool': True, u'list': [u'test', None]}]

res:
[ 'string', 25, False, { 'bool': True,  'list': [ 'test', None]}]
[u'string', 25, False, {u'bool': True, u'list': [u'test', None]}]

# inside the dictionary keys are sorted automatically by character asc. Keep this in mind.

""" 
